package plugins

import (
	"context"
	"fmt"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/klog/v2"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
)

const (
	Name = "sample-plugin"
	preFilterStateKey = "PreFilter" + Name
	// ErrReasonConstraintsNotMatch is used for PodTopologySpread filter error.
	ErrReasonConstraintsNotMatch = "node(s) didn't match pod topology spread constraints"
	// ErrReasonNodeLabelNotMatch is used when the node doesn't hold the required label.
	ErrReasonNodeLabelNotMatch = ErrReasonConstraintsNotMatch + " (missing required label)"
)

var _ framework.PreFilterPlugin = &Sample{}
var _ framework.FilterPlugin = &Sample{}

type Sample struct {
	args *SampleArgs
	handle *framework.Handle
}

type SampleArgs struct {
	FavoriteColor string `json:"favorite_color,omitempty"`
	FavoriteNumber int `json:"favorite_number,omitempty"`
	ThanksTo string `json:"thanks_to,omitempty"`
}

func getSampleArgs(obj runtime.Object) (*SampleArgs, error) {
	sa := &SampleArgs{}
	if err := frameworkruntime.DecodeInto(obj, sa);err != nil {
		return nil,err
	}
	return sa, nil
}

type preFilterState struct {
	framework.Resource  // requests, limits
}

func (s *preFilterState) Clone() framework.StateData{
	return s
}

func computerPodResourceLimit(pod *v1.Pod) *preFilterState {
	result := &preFilterState{}
	for _, container := range pod.Spec.Containers{
		result.Add(container.Resources.Limits)
	}
	return result
}

func getPreFilterState(state *framework.CycleState) (*preFilterState, error) {
	data, err := state.Read(preFilterStateKey)
	if err != nil {
		return nil, err
	}
	s, ok := data.(*preFilterState)
	if !ok {
		return nil, fmt.Errorf("%+v convert to SamplePlugin preFilterState error", data)
	}
	return s ,nil
}

func (s *Sample) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
	preState, err := getPreFilterState(state)
	if err != nil {
		return framework.NewStatus(framework.Error, err.Error())
	}
	// logic
	if nodeInfo.Node().Name == "k8s-node01" ||nodeInfo.Node().Name == "k8s-node02" || nodeInfo.Node().Name == "k8s-node03" {
		klog.V(3).InfoS("Node doesn't have required label", "node", klog.KObj(nodeInfo.Node()), "label", "123")
		return framework.NewStatus(framework.UnschedulableAndUnresolvable, ErrReasonNodeLabelNotMatch)
	}
	klog.V(3).Infof("filter pod : %v, node: %v, pre state: %v", pod.Name, nodeInfo.Node().Name, preState)
	return framework.NewStatus(framework.Success)
}

func (s *Sample) PreFilter(ctx context.Context, state *framework.CycleState, p *v1.Pod) *framework.Status {
	klog.V(3).Infof("preFilter pod %v", p.Name)
	state.Write(preFilterStateKey, computerPodResourceLimit(p))
	return nil
}

func (s *Sample) PreFilterExtensions() framework.PreFilterExtensions {
	return nil
}

// type PluginFactory = func(configuration runtime.Object, f framework.Handle) (framework.Plugin, error)
func New(obj runtime.Object, f framework.Handle) (framework.Plugin, error) {
	args, err := getSampleArgs(obj)
	if err != nil {
		return nil,err
	}

	// validate args
	klog.V(3).Infof("get plugin config args: %+v", args)
	return &Sample{
		args: args,
		handle: &f,
	}, nil
}

func (s *Sample) Name() string {
	return Name
}